/**
 * 维护“群”联系人
 */
import { tuple, cancellable, success } from '@gnlab/utils'
import { MemSubject, mmap } from '@gnlab/utils/rx'
import { OrderedMap } from 'immutable'
import { filter, first } from 'rxjs/operators'
import type { BaseContact } from '@/services/contacts'
import { makeContactId, ContactType, detectIsGroup, isWechatWxid } from '@/services/contacts'
import type { LoadInfoConditions } from '../common/ContactTypeService'
import { ContactTypeService } from '../common/ContactTypeService'
import type { HookCreatedGroup } from './api'
import {
  loadGroupMembers,
  addGroupMember,
  removeGroupMember,
  applyGroupMember,
  getMuteGroups,
  muteGroup,
  cancelMuteGroup,
  createGroup,
  updateGroupNotice,
  getGroupExtra,
  updateGroupExtra,
  getGroupTags,
  addGroupTag,
  removeGroupTag,
  getBoundGroupTags,
  bindGroupTag,
  unbindGroupTag,
  getGroups,
  formatHookCreatedGroup,
} from './api'
import type {
  GroupContact,
  GroupExtra,
  MuteGroup,
  GroupEvent,
  GroupTag,
  GroupSearching,
} from './definition'
import { GroupEventType } from './definition'

export * from './definition'

export class Groups extends ContactTypeService<GroupContact, GroupEvent> {
  protected get qywxs() {
    return this.services.qywxs
  }
  private get communicate() {
    return this.services.mqtt.communicate
  }
  private get auth() {
    return this.services.auth
  }
  private get contacts() {
    return this.services.contacts
  }

  constructor() {
    super('groups')
  }

  registered() {
    super.registered()

    this.initAutoLoadMembers()

    void this.auth.bizAccountIds.then(() => {
      void this.loadMutes()
      void this.loadTags()
    })

    this.qywxs.notAlived.subscribe(info => this.excludeQywx(info.wxid))
  }

  /**
   * 当前群
   * 若当前联系人不是群，此值为 null
   */
  protected get current() {
    const current = this.contacts.current.contact.value
    return current && current.type === ContactType.group ? current : null
  }
  /**
   * ============================================
   * 维护联系人列表
   * ============================================
   */

  // 群是一次性加载完的
  get pageSize() {
    return 100000
  }

  async request({ wxids }: { wxids: string[] }) {
    return getGroups(wxids)
  }

  async tryLoadInfo({ id, ownerWxid, contactUserId, byChatRecord }: LoadInfoConditions) {
    if (!detectIsGroup(contactUserId)) return null

    await this.loadForQywxs([ownerWxid])
    const group = this.list.value.find(v => v.id === id)
    if (!group) return false

    // 加载到的最近联系时间/内容可能不是最新的，此时用 byChatRecord 代替
    if (byChatRecord && group.contacted < byChatRecord.timestamp) {
      this.listAddOrUpdate({
        ...group,
        contacted: byChatRecord.timestamp,
        latestChat: byChatRecord,
      })
    }
    return true
  }

  initEventEmitter() {
    // 处理“新群创建成功”事件
    this.communicate.hook<HookCreatedGroup>(11074).subscribe(async item => {
      const wxid = item.message.wxid!
      const contactId = makeContactId(wxid, item.data.room_conversation_id)
      if (this.list.value.has(contactId)) return

      this.logger.info('发现新创建的群', { wxid, ...item.data })
      this.listAddOrUpdate(formatHookCreatedGroup(wxid, item.data)) // 先填充一个临时群联系人，然后完全重新加载群列表
      await this.loadForQywxs([wxid])
      this._events.next({ type: GroupEventType.Created, contactId })
    })

    // 处理“群重命名”事件
    interface HookGroupRename {
      op_user_id: string
      room_conversation_id: string
      room_name: string
    }
    this.communicate.hook<HookGroupRename>(11078).subscribe(item => {
      const contactId = makeContactId(item.message.wxid ?? '', item.data.room_conversation_id)
      const group = this.list.value.get(contactId)
      if (!group) return void this.logger.info('找不到群，无法处理群重命名消息', item.data)
      this.listAddOrUpdate({ ...group, name: item.data.room_name })
    })
  }

  /**
   * ========================
   * 群成员
   * ========================
   */
  members = new MemSubject<BaseContact[]>([])

  // 群成员里有没有个人微信（是否有个人微信决定了消息发出后多久内允许撤回）
  hasWechatMember = this.members.pipe(mmap(members => !!members.find(m => isWechatWxid(m.userId))))

  /**
   * 当前联系人是群时，自动加载成员列表
   */
  protected initAutoLoadMembers() {
    this.contacts.current.id.subscribe(() => void this.loadMembers())
  }

  /**
   * 加载当前群的成员
   */
  async loadMembers() {
    const { current } = this
    if (current) {
      this.members.next([])
      const res = await loadGroupMembers(current.id)

      const newestCurrentId = this.current?.id
      if (newestCurrentId !== current.id) return // 如果在成员列表加载完成前已经切换了联系人，则不再写入列表

      this.members.next(res.success ? res.data : [])
    } else if (this.members.value.length) {
      // 重置群成员列表
      this.members.next([])
    }
  }

  /**
   * 向当前群添加群成员
   */
  async addMembers(wxids: string[]) {
    const { current } = this
    if (!current) return
    const res = await addGroupMember(current.id, wxids)
    setTimeout(() => void this.loadMembers(), 3000) // 添加群成员后要过一会儿才能加载到
    return res
  }

  /**
   * 移除当前群的群成员
   */
  async removeMember(wxids: string[]) {
    const { current } = this
    if (!current) return
    const res = await removeGroupMember(current.id, wxids)
    setTimeout(() => void this.loadMembers(), 3000)
    return res
  }

  /**
   * 添加当前群的群成员为好友
   */
  async applyMember(userId: string, verify: string) {
    const { current } = this
    if (!current) return
    const res = await applyGroupMember(current.id, userId, verify)
    setTimeout(() => void this.loadMembers(), 3000)
    return res
  }

  /**
   * ========================
   * 群资料
   * ========================
   */

  /**
   * 获取群扩展信息
   */
  async getExtra(contactId?: string) {
    if (contactId === undefined) contactId = this.current!.id
    return getGroupExtra(contactId)
  }

  /**
   * 更新群资料
   */
  async update(params: {
    id?: string
    remark: GroupContact['remark']
    describe: GroupExtra['describe']
  }) {
    const { id = this.current!.id, remark, describe } = params

    const updated = { ...this.list.value.get(id)!, remark }
    this.listAddOrUpdate(updated)

    return updateGroupExtra(id, { remark, describe })
  }

  /**
   * ========================
   * 群静音
   * ========================
   */
  mutes = new MemSubject<OrderedMap<string, MuteGroup>>(OrderedMap()) // contactId => mute record

  protected async loadMutes() {
    const res = await getMuteGroups()
    if (res.success) {
      this.mutes.next(
        OrderedMap(res.data.map(item => [makeContactId(item.wxid, item.conversationId), item])),
      )
    }
  }

  async mute(contactId: string) {
    const res = await muteGroup(contactId)
    void this.loadMutes()
    return res
  }

  async cancelMute(muteId: number) {
    const res = await cancelMuteGroup(muteId)
    void this.loadMutes()
    return res
  }

  /**
   * ========================
   * 群标签
   * ========================
   */
  private readonly _tags = new MemSubject<GroupTag[]>([])
  readonly tags = this._tags.asStream()

  // 加载群标签
  async loadTags() {
    const res = await getGroupTags()
    if (res.success) this._tags.next(res.data)
    return res
  }

  // 创建群标签
  async addTag(name: string) {
    const res = await addGroupTag(name)
    if (res.success) this._tags.next([res.data, ...this.tags.value])
    void this.loadTags()
    return res
  }

  // 删除群标签
  async removeTag(id: number) {
    const res = await removeGroupTag(id)
    if (res.success) this._tags.next(this.tags.value.filter(t => t.id !== id))
    void this.loadTags()
    return res
  }

  // 返回拥有指定标签的群
  async getWithTags(tagIds: number[], wxids?: string[]) {
    if (!wxids) wxids = this.qywxs.usableWxids.value
    return getGroups(wxids, tagIds)
  }

  // 获取指定群绑定了的标签
  async getBoundTags(contactId?: string) {
    return getBoundGroupTags(contactId ?? this.current!.id)
  }

  // 绑定标签
  async bindTag(contactId: string, tagId: number) {
    return bindGroupTag(contactId, tagId)
  }

  // 解绑标签
  async unbindTag(contactId: string, tagId: number) {
    return unbindGroupTag(contactId, tagId)
  }

  /**
   * 群搜索（基于群标签）
   */

  protected _searching = new MemSubject<GroupSearching>(null) // 为 null 代表未触发搜索
  searching = this._searching.asStream()

  async searchByTags(tagIds: number[], wxids?: string[]) {
    const prevSearching = this.searching.value
    if (prevSearching?.loading) prevSearching.loading.cancel() // 执行新搜索前，终止之前未完成的搜索

    const request = cancellable(this.getWithTags(tagIds, wxids))
    this._searching.next({ tagIds, wxids, loading: request, items: null })

    void request.then(res => {
      if (res.success) {
        const items = OrderedMap(res.data.map(item => tuple(item.id, item)))
        this._searching.next({ tagIds, wxids, loading: null, items })
      } else {
        this._searching.next({ tagIds, wxids, loading: null, items: OrderedMap() })
      }
    })

    return request
  }

  // 清空搜索
  clearSearch() {
    const searching = this.searching.value
    if (searching?.loading) searching.loading.cancel()
    this._searching.next(null)
  }

  // 从搜索结果中排除指定企业微信
  private excludeQywx(wxid: string) {
    const searching = this._searching.value
    if (searching?.items) {
      this._searching.next({
        ...searching,
        items: searching.items.filter(item => item.ownerWxid !== wxid),
      })
    }
  }

  /**
   * ========================
   * 其他
   * ========================
   */

  /**
   * 创建一个群
   */
  async create(wxid: string, members: string[]) {
    const res = await createGroup(wxid, members)
    if (res.success) {
      // 将此群设为当前联系人
      const contactId = makeContactId(wxid, res.data.conversationId)
      if (this.list.value.has(contactId)) {
        // 如果此时列表里已经有这个群（意味着群创建成功的 mqtt 消息在这个接口结束前先推送了过来）
        this.contacts.current.mark(contactId)
      } else {
        // 如果当前群列表里还没有这个群，则等待群创建成功事件
        this.events
          .pipe(
            filter(event => event.contactId === contactId),
            first(),
          )
          .subscribe(() => {
            this.contacts.current.mark(contactId)
          })
      }
    }
    return res
  }

  /**
   * 批量设置群公告
   */
  async updateNotice(...args: Parameters<typeof updateGroupNotice>) {
    return updateGroupNotice(...args)
  }

  /**
   * 把联系人拉入指定群
   */
  async pullIn(
    groupId: string, // 群的 conversation id
    contactUserId: string, // 要拉入的联系人的 user id
  ) {
    return addGroupMember(groupId, [contactUserId])
  }

  /**
   * 有管理员权限的用户获取所有账号登录了的企业微信的群列表
   * wxids: 只获取指定企业微信的群列表
   * filter: 只返回符合条件的群（及对应的 qywx 和 account）
   */
  async adminGetAllAccountGroups(
    wxids?: string[] | null,
    filter?: (group: GroupContact) => boolean,
  ) {
    const { accounts, qywxs } = await this.qywxs.getAllAccountQywxs()
    const res = await getGroups(wxids ?? qywxs.map(q => q.wxid))
    if (!res.success) return res

    const groups = res.data
    const filteredGroups = filter ? groups.filter(g => filter(g)) : groups

    const appearedWxids = new Set(filteredGroups.map(g => g.ownerWxid))
    const filteredQywxs = qywxs.filter(q => appearedWxids.has(q.wxid))

    const appearedAccountIds = new Set(filteredQywxs.map(q => q.accountId))
    const filteredAccounts = accounts.filter(v => appearedAccountIds.has(v.id))

    return success({
      groups: filteredGroups,
      qywxs: filteredQywxs,
      accounts: filteredAccounts,
    })
  }
}
